from __future__ import print_function
from __future__ import absolute_import

import stackless
import sys
import math
import logging
import traceback
import math
import cStringIO as cstringio
import io
import os
from os import path

import wx
from wx import glcanvas
from wx import propgrid as wxpg

from kharlia import wxinter, core, gamewindow
from kharlia.engine.base import FSizeI, FRectI, FRectF, FVector3F, FVector2I
from kharlia.engine.client import FResource, FTexture
from kharlia.engine.actor import (GetComponentClasses, EDirection, CActor,
                                  AComponent, ATransform, ACamera, ASprite)
from kharlia.engine.level import (FTileData, FTileset, TileFromCoords, TileToX,
                                  TileToY, CLevel)
from kharlia.engine.main import CEngine
from kharlia.engine.world import CWorld

from .generated import *

# Generated IDs are 10000 to 19999
ID_MANUAL_START = 4000
ID_MODE_TILE = 4001
ID_MODE_ACTOR = 4002
ID_NEW_ACTOR = 4003
ID_COMPONENT_FIRST = 8000
ID_COMPONENT_LAST = 8999

# Logger object for the editor
GLogger = None

def GetPlaceableComponentNames():
    '''Return list of class names for placeable components.'''
    names = []
    for i in GetComponentClasses().itervalues():
        if i.PlaceableClass:
            names.append(i.__name__)
    names.sort()
    return names

def GetPlaceableComponentClasses():
    cls = []
    for i in GetComponentClasses().itervalues():
        if i.PlaceableClass:
            cls.append(i)
    return cls

def GetComponentClass(name):
    return GetComponentClasses().get(name, None)

def GenerateTileCodes(x, y, width, height):
    '''Generate a list of tile codes for the specified rectangle.'''
    tiles = []
    tiles_append = tiles.append
    convert = TileFromCoords
    for i in xrange(height):
        for j in xrange(width):
            tiles_append(convert(x + j, y + i))
    return tiles

class CEditorViewport(gamewindow.CViewport):
    Frame = core.CWeakRefDescr('_Frame')

    def __init__(self, frame, *args, **kwds):
        gamewindow.CViewport.__init__(self, *args, **kwds)
        self.LastX = 0
        self.LastY = 0
        self.Dragging = False
        self.Frame = frame
        self.MenuOpenPos = None

        # Create actor mode right-click menu
        self.ActorModeMenu = am = wx.Menu()
        am.Append(ID_NEW_ACTOR, u'New actor', u'Create a new actor at this position.')

        self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnMouseCaptureLost)
        self.Bind(wx.EVT_MENU, self.OnMenuNewActor, id=ID_NEW_ACTOR)
        
    def OnClientInit(self, client):
        super(CEditorViewport, self).OnClientInit(client)
    
    def OnLeftMouseDown(self, evt):
        super(CEditorViewport, self).OnLeftMouseDown(evt)
        
        # Avoid issue with clicking on viewport after opening popup menu
        if self.HasCapture():
            self.ReleaseMouse()

        if not self.Engine.LevelLoaded:
            return
        
        # If we're playing in-editor we just focus the viewport.
        if self.Engine.Playing:
            self.SetFocus()
            return

        # If we're in tile editing mode: apply the brush to the level if possible,
        # ignore a click in a tile selection, or clear the tile selection.
        if self.Engine.Mode == CEditorEngine.MODE_TILE:
            brush = self.Engine.TileBrush
            selection = self.Engine.TileSelection
            if brush:
                mousepos = FVector2I(evt.GetX(), evt.GetY())
                tilepos = self.Engine.ConvertScreenToTileCoords(mousepos)
                rect = FRectF(math.floor(tilepos.X),
                              math.floor(tilepos.Y),
                              brush.Size.Width,
                              brush.Size.Height)
                self.Engine.TileSelection = rect
                self.Engine.ApplyTileBrush(rect.Pos)
            elif selection:
                mousex, mousey = self.Engine.ConvertScreenToTileCoords((evt.GetX(), evt.GetY()))
                if (mousex >= selection.Left and
                        mousex <= selection.Right and
                        mousey >= selection.Top and
                        mousey <= selection.Bottom):
                    pass
                else:
                    del self.Engine.TileSelection
            else:
                del self.Engine.TileSelection
        # If we're in actor editing mode, select the actor we clicked.
        elif self.Engine.Mode == CEditorEngine.MODE_ACTOR:
            self.Engine.SelectActorAtScreen(FVector2I(evt.GetX(), evt.GetY()))
        else:
            raise NotImplementedError()

    def OnRightMouseDown(self, evt):
        if not self.Engine.LevelLoaded:
            return
        if self.Engine.TileBrush is not None:
            del self.Engine.TileBrush
        if self.Engine.Mode == CEditorEngine.MODE_TILE:
            self.Dragging = True
        elif self.Engine.Mode == CEditorEngine.MODE_ACTOR:
            x, y = evt.GetPositionTuple()
            self.Engine.SelectActorAtScreen(FVector2I(x, y))
            self.MenuOpenPos = (x, y)
            self.PopupMenu(self.ActorModeMenu)

        super(CEditorViewport, self).OnRightMouseDown(evt)

    def OnRightMouseUp(self, evt):
        if self.Dragging:
            self.Dragging = False
        super(CEditorViewport, self).OnRightMouseUp(evt)

    def OnMouseCaptureLost(self, evt):
        pass

    def OnMouseMotion(self, evt):
        if self.Dragging:
            assert self.Engine.LevelLoaded
            vec = FVector3F((float(self.LastX) - float(evt.GetX())) / 16,
                            (float(self.LastY) - float(evt.GetY())) / 16,
                            0.0)
            self.Engine.MoveCamera(vec)
        self.LastX = evt.GetX()
        self.LastY = evt.GetY()
        super(CEditorViewport, self).OnMouseMotion(evt)
        
    def OnKeyDown(self, event):
        super(CEditorViewport, self).OnKeyDown(event)
    
    def OnKeyUp(self, event):
        super(CEditorViewport, self).OnKeyUp(event)

    def OnMenuNewActor(self, event):
        x, y = self.MenuOpenPos
        self.Engine.CreateActorAtScreen(FVector2I(x, y))

class CTileSetPanel(wx.Panel):
    def __init__(self, engine, tileset, *args, **kwds):
        wx.Window.__init__(self, *args, **kwds)
        self.Engine = engine
        self.TilesetImage = wx.Bitmap(tileset)
        self.SetBackgroundColour(wx.Colour(40,40,40))
        self.currentThickness = 1
        self.Dragging = False
        self.selecting = True
        self.SelectPos = None
        self.SelectSize = None
        self.Selection = FRectI()
        self.Reselecting = False
        self.Buffer = None
        for event, handler in [
            (wx.EVT_PAINT, self.OnPaint),
            (wx.EVT_LEFT_DOWN, self.OnLeftMouseDown),
            (wx.EVT_LEFT_UP, self.OnLeftMouseUp),
            (wx.EVT_SIZE, self.OnSize),
            (wx.EVT_MOTION, self.OnMouseMotion),
            (wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
            ]:
            self.Bind(event, handler)
        self.InitBuffer()
        self.DrawTileset()
        self.Refresh()
        self.SetSize(wx.Size(self.TilesetImage.GetWidth(),
                             self.TilesetImage.GetHeight()))
        
    def InitBuffer(self):
        '''Create the empty bitmap and buffered DC that will contain the tileset image.'''
        self.Buffer = wx.EmptyBitmap(self.TilesetImage.GetWidth(),
                                     self.TilesetImage.GetHeight())
        dc = wx.BufferedDC(None, self.Buffer)
        dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
        dc.Clear()

    #def Reset(self):
        #dc = wx.BufferedDC(None, self.Buffer)
        #mdc = wx.MemoryDC(self.TilesetImage)
        #dc.Blit(0, 0, self.TilesetImage.GetWidth(),
        #              self.TilesetImage.GetHeight(), mdc, 0, 0)
        #self.DrawTileset()
        #self.Refresh() # re paint

    def OnEraseBackground(self, event):
        pass
    
    def OnPaint(self, event):
        wx.BufferedPaintDC(self, self.Buffer)

    def OnSize(self, event):
        self.SetSize(wx.Size(self.TilesetImage.GetWidth(),
                             self.TilesetImage.GetHeight()))
    
    def DrawTileset(self):
        '''Draw the tileset image(s) onto our buffered DC.'''
        dc = wx.BufferedDC(None, self.Buffer)
        mdc = wx.MemoryDC(self.TilesetImage)
        dc.Blit(0, 0, self.TilesetImage.GetWidth(),
                      self.TilesetImage.GetHeight(), mdc, 0, 0)

    def DrawSelection(self):
        '''Draw the selection box onto our buffered DC.'''
        selection = self.Selection
        dc = wx.BufferedDC(None, self.Buffer)
        dc.SetBrush(wx.Brush(wx.Colour(255, 0, 0), wx.TRANSPARENT))
        dc.SetLogicalFunction(wx.INVERT)
        if (selection.Width != 0 and selection.Height != 0 and 
                dc is not None):
            dc.DrawRectangle(selection.X, selection.Y,
                             selection.Width, selection.Height)
            dc.DrawRectangle(selection.X - 1, selection.Y - 1,
                             selection.Width + 2, selection.Height + 2)
        
    def OnMouseMotion(self, event):
        if self.Dragging:
            select = event.GetPositionTuple()
            
            # undo last select draw
            if self.Selection.Area > 0:
                self.DrawSelection()
            
                
            #self.SelectSize = [(math.floor((select[0]+16)/16)*16)-self.Selection.X \
            #                  ,(math.floor((select[1]+16)/16)*16)-self.Selection.Y]
            #sw = math.ceil(float(select[0]) / 16) * 16
            sw = (math.floor((select[0] + 16) / 16) * 16) - self.Selection.X
            sh = (math.floor((select[1] + 16) / 16) * 16) - self.Selection.Y
            self.Selection.Size = FSizeI(int(sw), int(sh))

            #self.DrawTileset()
            self.DrawSelection()
            self.Refresh()
    
    def OnLeftMouseDown(self, event):
        select = event.GetPositionTuple()
        
        # undo last draw
        if self.Selection.Area > 0:
            mx, my = event.GetX(), event.GetY()
            print('MX:', mx, 'MY:', my)
            print('SP:', self.Selection.Pos)
            print('SS:', self.Selection.Size)
            if mx >= self.Selection.X and \
               mx <  self.Selection.X + self.Selection.Width and \
               my >= self.Selection.Y and \
               my <  self.Selection.Y + self.Selection.Height:
               print('DOING RESELECT')
               self.Reselecting = True
               reduced = FRectI(self.Selection)
               reduced /= FRectI(16, 16, 16, 16)
               tiles = GenerateTileCodes(reduced.X,
                                         reduced.Y,
                                         reduced.Width,
                                         reduced.Height)
               self.Engine.TileBrush = (reduced.Width, reduced.Height, tiles)
               return

        self.Selection.Pos = (int(select[0] / 16) * 16,
                              int(select[1] / 16) * 16)
        self.Selection.Size = FSizeI(0, 0)
        print('SP:', self.Selection.Pos)
        self.DrawTileset()
        self.Dragging = True
        self.CaptureMouse()
        self.OnMouseMotion(event)
    
    def OnLeftMouseUp(self, event):
        if self.HasCapture():
            self.ReleaseMouse()
        
        if self.Reselecting:
            self.Dragging = False
            self.Reselecting = False
            return
        
        selection = self.Selection

        # straighted out a reversed rectangle
        if selection.Width < 0:
            selection.Width = abs(selection.Width)
            selection.X -= selection.Width
        if selection.Height < 0:
            selection.Height = abs(selection.Height)
            selection.Y -= selection.Height

        # clamp to tileset panel
        if selection.X < 0:
            selection.Width += selection.X
            selection.X = 0
        if selection.Y < 0:
            selection.Height += selection.Y
            selection.Y = 0
        tsw, tsh = self.TilesetImage.GetWidth(), self.TilesetImage.GetHeight()
        if selection.Right > tsw:
            selection.Width -= selection.Right - tsw
        if selection.Bottom > tsh:
            selection.Height -= selection.Bottom - tsh

        self.Dragging = False

        ## Convert the coordinates for the selected tile area to tile codes
        if selection.Area > 0:
            tiles = []
            reducedX = int(selection.X / 16)
            reducedY = int(selection.Y / 16)
            reducedWidth = int(selection.Width / 16)
            reducedHeight = int(selection.Height / 16)
            for i in xrange(reducedHeight):
                for j in xrange(reducedWidth):
                    tiles.append(TileFromCoords(reducedX + j, reducedY + i))
            self.Engine.TileBrush = (reducedWidth, reducedHeight, tiles)
        else:
            del self.Engine.TileBrush

class CNewLevelDialog(CNewLevelDialogBase):
    def __init__(self, parent, callback):
        CNewLevelDialogBase.__init__(self, parent)
        
        tgwb = self.FindWindowById(ID_TILEGROUP_WIDTH)
        tghb = self.FindWindowById(ID_TILEGROUP_HEIGHT)
        tgwb.Value = u'64'
        tghb.Value = u'64'
        
        self.Title = u'New Level'
        self.Callback = callback
        GLogger.info('created new map dialog')
        self.Bind(wx.EVT_BUTTON, self.OnOK, id=wx.ID_OK)
        
    def OnOK(self, event):
        mname = self.FindWindowById(ID_NAME).GetValue()
        assert isinstance(mname, unicode)
        mw = int(self.FindWindowById(ID_WIDTH).GetValue())
        mh = int(self.FindWindowById(ID_HEIGHT).GetValue())
        #lw = int(self.FindWindowById(ID_TILEGROUP_WIDTH).GetValue())
        #lh = int(self.FindWindowById(ID_TILEGROUP_HEIGHT).GetValue())
        #print('new level params:', mname, mw, mh, lw, lh)
        self.Close()
        self.Callback(mname, mw, mh)
        #self.Callback(mname, mw, mh, lw, lh)

#class CFlagsListProperty(wxpg.PGProperty):
    #def __init__(self,
    #def ChildChanged(self, tv, ci, cv):
    #    print('CHILD CHANGED:', tv, ci, cv)
    #    return wxpg.FlagsProperty.ChildChanged(self, tv, ci, cv)

def UpdateObjectProperties(root, vars, source):
    '''Update all child properties of root named in vars from source object.'''
    for var in vars:
        vname = var['name']
        vuname = unicode(vname)
        prop = root.GetPropertyByName(vuname)
        UpdateObjectProperty(prop, var, source)

def UpdateObjectProperty(prop, var, source):
    '''Update the property value with the value from the object.'''
    vname = var['name']
    vtype = var['type']
    if vtype is FVector3F:
        vec = getattr(source, vname)
        xprop = prop.GetPropertyByName(u'X')
        yprop = prop.GetPropertyByName(u'Y')
        zprop = prop.GetPropertyByName(u'Z')
        xprop.SetValue(vec.X)
        yprop.SetValue(vec.Y)
        zprop.SetValue(vec.Z)
    elif vtype is EDirection:
        dir = getattr(source, vname)
        prop.SetValue(str(dir))
    elif vtype is FTexture:
        res = getattr(source, vname)
        if res is None:
            prop.SetValue(u'')
        else:
            prop.SetValue(unicode(res.Name))
    elif vtype is AComponent:
        tcomp = getattr(source, vname)
        prop.SetValue(ReprRef(tcomp))
    else:
        value = getattr(source, vname)
        prop.SetValue(value)

def UpdateObjectValue(prop, var, target):
    '''Update an object value with the value of the property object.'''
    vname = var['name']
    vtype = var['type']
    vuname = unicode(vname)
    if vtype in {int, unicode, float, bool}:
        setattr(target, vname, prop.GetValue())
    elif vtype is str:
        # unicode -> str
        setattr(target, vname, str(prop.GetValue()))
    else:
        raise NotImplementedError()

def ReprRef(value):
    '''Represent an actor or component reference as a string that can be parsed.'''
    if value is None:
        return u''
    elif isinstance(value, CActor):
        tag = value.Tag if value.Tag else u''
        return u'a:{0}:{1}'.format(value.ID, tag)
    elif isinstance(value, AComponent):
        act = value.Actor
        tag = act.Tag if act.Tag else u''
        return u'c:{0}:{1}:{2}'.format(act.ID, tag, type(value).__name__)
    else:
        return repr(value)

def MakeObjectProperties(root, vars):
    '''Create child property objects for root using specified variable info.'''
    grid = root.GetGrid()
    for var in vars:
        vname = var['name']
        vtype = var['type']
        vdefault = var.get('default', None)
        vdefinit = var.get('definit', False)
        vconst = var.get('const', False)
        vuname = unicode(vname)
        p = None

        if vtype is int:
            p = root.AppendChild(wxpg.IntProperty(vuname, value=0))
        elif vtype is str or vtype is unicode:
            p = root.AppendChild(wxpg.StringProperty(vuname, value=u''))
        elif vtype is float:
            p = root.AppendChild(wxpg.FloatProperty(vuname, value=0.0))
        elif vtype is bool:
            p = root.AppendChild(wxpg.BoolProperty(vuname, value=False))
            p.SetAttribute(wxpg.PG_BOOL_USE_CHECKBOX, True)
        # Extended types
        elif vtype is FVector3F:
            p = root.AppendChild(wxpg.StringProperty(vuname,
                                                     value=u'<composed>'))
            p.AppendChild(wxpg.FloatProperty(u'X', value=0.0))
            p.AppendChild(wxpg.FloatProperty(u'Y', value=0.0))
            p.AppendChild(wxpg.FloatProperty(u'Z', value=0.0))
        elif vtype is FTexture:
            p = root.AppendChild(wxpg.FileProperty(vuname, value=u''))
            p.SetAttribute(wxpg.PG_FILE_SHOW_FULL_PATH, False)
        # Object references
        # TODO: Make a chooser dialog for actors or components
        elif vtype is AComponent:
            p = root.AppendChild(wxpg.StringProperty(vuname, value=u''))
            grid.SetPropertyReadOnly(p, True)
        elif vtype is CActor:
            p = root.AppendChild(wxpg.StringProperty(vuname, value=u''))
            grid.SetPropertyReadOnly(p, True)

        if p:
            if vconst:
                grid.SetPropertyReadOnly(p, True)
            p.SetClientObject(var)

def ShowErrorMessage(msg, title=u'Error'):
    wx.MessageBox(msg, title)

class CEditorMainFrame(wx.Frame):
    # parent, id, title, pos, size, style
    def __init__(self, engine):
        wx.Frame.__init__(self, parent=None, title='')
        self.EdGridVisible = False
        self.Engine = engine

        # Create the main dialog, menu and status bars
        CMainPanel(self)
        self.SetMenuBar(CMainMenuBar())
        self.CreateStatusBar()
        self.SetStatusText(u'Ready...')

        # Create the toolbar and populate it
        self.CreateToolBar()
        tb = self.ToolBar
        assert tb
        tb.AddLabelTool(wx.ID_NEW, u'New Level',
                        wx.ArtProvider.GetBitmap(id=wx.ART_NEW,
                                                 client=wx.ART_TOOLBAR),
                        shortHelp=u'New level...')
        tb.AddSeparator()
        tb.AddRadioLabelTool(ID_MODE_TILE, u'Tile Mode',
                             wx.ArtProvider.GetBitmap(id=wx.ART_GO_BACK,
                                                      client=wx.ART_TOOLBAR),
                             shortHelp=u'Tile editing mode')
        tb.AddRadioLabelTool(ID_MODE_ACTOR, u'Actor Mode',
                             wx.ArtProvider.GetBitmap(id=wx.ART_GO_FORWARD,
                                                      client=wx.ART_TOOLBAR),
                             shortHelp=u'Actor editing mode')
        tb.Realize()

        # Configure the splitter
        splitter = self.FindWindowById(ID_MAIN_SPLITTER)
        splitter.SetSashGravity(1.0)

        # Replace the viewport placeholder
        vp_holder = self.FindWindowById(ID_VIEWPORT)
        vp_sizer = vp_holder.GetContainingSizer()
        vp_parent = vp_holder.GetParent()
        vp_holder.Destroy()
        self.Viewport = CEditorViewport(self, parent=vp_parent,
                                        id=ID_VIEWPORT, style=wx.WANTS_CHARS)
        vp_sizer.Add(self.Viewport, proportion=1, flag=wx.EXPAND)
        
        # Set the viewport's tab order
        notebook = self.FindWindowById(ID_MAIN_NOTEBOOK)

        # Replace the tileset placeholer with an actual tileset
        tileset_holder = self.FindWindowById(ID_TILESET)
        tileset_sizer = tileset_holder.GetContainingSizer()
        tileset_parent = tileset_holder.GetParent()
        tileset_holder.Destroy()

        # Make a scrolledwindow widget with scrollbars always on
        scrolling_window = wx.ScrolledWindow(tileset_parent,
                                             style=wx.HSCROLL | wx.VSCROLL|
                                                   wx.ALWAYS_SHOW_SB) 
        scrolling_window.SetScrollRate(1, 1)
        scrolling_window.EnableScrolling(True, True)

        # Add it to tileset's flexgrid sizer
        tileset_sizer.Add(scrolling_window, proportion=1, flag=wx.EXPAND)

        # Make a tileset widget and put it in the scrolling window
        tilesfilename = path.join(engine.Config['DataPath'],
                                  engine.Config['TestTilesetFileName'])
        self.tileset = CTileSetPanel(engine, tilesfilename, parent=scrolling_window)

        # Make a sizer for the tileset inside of the scrolled window
        tile_sizer = wx.BoxSizer(wx.VERTICAL)
        tile_sizer.Add(self.tileset)#, proportion=1, flag=wx.EXPAND)

        # Assign the client sizer
        scrolling_window.SetSizer(tile_sizer)
        scrolling_window.SetBackgroundColour(wx.Colour(50, 50, 50))

        # Create the actor property grid
        ao_dummy = self.FindWindowById(ID_ACTOR_PROPS)
        assert ao_dummy, 'actor option placeholder not found'
        ao_sizer = ao_dummy.ContainingSizer
        ao_parent = ao_dummy.Parent
        ao_dummy.Destroy()
        del ao_dummy

        ao = wxpg.PropertyGrid(parent=ao_parent,
                               id=ID_ACTOR_PROPS,
                               style=wxpg.PG_AUTO_SORT |
                                     wxpg.PG_SPLITTER_AUTO_CENTER |
                                     wxpg.PG_DEFAULT_STYLE)
        ao_sizer.Add(ao, proportion=1, flag=wx.EXPAND | wx.ALL)
        ao.Append(wxpg.PropertyCategory(label=u'(Actor)'))
        ao.Append(wxpg.StringProperty(label=u'Owner', value=u'(None)'))
        ao.Append(wxpg.StringProperty(label=u'Tag', value=u'(None)'))
        ao.Append(wxpg.IntProperty(label=u'ID', value=0))
        ao.SetPropertyReadOnly(u'ID', True)
        ao.SetPropertyAttributeAll(wxpg.PG_BOOL_USE_CHECKBOX, True)
        self.ActorProperties = ao
        self.ActorPropertiesInited = False

        # Locate component menu component menu
        self.ComponentMenu = self.MenuBar.m_comp_menu

        
        # Create the layer property grid
        lop_dummy = self.FindWindowById(ID_LAYER_OPTIONS)
        assert lop_dummy, 'layer option placeholder not found'
        lop_sizer = lop_dummy.ContainingSizer
        lop_parent = lop_dummy.Parent
        lop_dummy.Destroy()
        del lop_dummy
        
        lop_pg = wxpg.PropertyGrid(
            parent=lop_parent,
            id=ID_LAYER_OPTIONS,
            style=wxpg.PG_AUTO_SORT |
                  wxpg.PG_SPLITTER_AUTO_CENTER |
                  wxpg.PG_DEFAULT_STYLE
        )
        lop_sizer.Add(lop_pg, proportion=1, flag=wx.EXPAND | wx.ALL)
        cat = lop_pg.Append(wxpg.PropertyCategory(label=u'Layer'))
        vars = (
            dict(name='Index', type=int, const=True),
            dict(name='Name', type=unicode),
            dict(name='Visible', type=bool, default=True),
            dict(name='NoCollision', type=bool, default=False),
            dict(name='Alpha', type=int, default=1),
            )
        self.LayerVars = vars
        self.LayerProperties = lop_pg
        MakeObjectProperties(cat, vars)

        self.SceneGrid = self.FindWindowById(ID_SCENE_GRID)
        self.UpdateSceneGrid()

        # Resize and bind now that the frame is setup
        self.SetSize(wx.Size(1200, 800))
        # File menu
        self.Bind(wx.EVT_MENU, self.OnNewLevel, id=wx.ID_NEW)
        self.Bind(wx.EVT_MENU, self.OnOpenLevel, id=wx.ID_OPEN)
        self.Bind(wx.EVT_MENU, self.OnSaveLevel, id=wx.ID_SAVE)
        self.Bind(wx.EVT_MENU, self.OnSaveLevelAs, id=wx.ID_SAVEAS)
        self.Bind(wx.EVT_MENU, self.OnCloseLevel, id=wx.ID_CLOSE)
        self.Bind(wx.EVT_MENU, self.OnQuit, id=wx.ID_EXIT)
        # View
        self.Bind(wx.EVT_MENU, self.OnToggleGrid, id=ID_SHOW_GRID)
        # Level
        self.Bind(wx.EVT_MENU, self.OnPlay, id=ID_PLAY)
        self.Bind(wx.EVT_MENU, self.OnEndPlay, id=ID_END_PLAY)
        # Component
        self.Bind(wx.EVT_MENU_RANGE, self.OnComponentMenu,
                  id=ID_COMPONENT_FIRST, id2=ID_COMPONENT_LAST)
        # Help
        self.Bind(wx.EVT_MENU, self.OnAbout, id=wx.ID_ABOUT)
        # Test
        self.Bind(wx.EVT_MENU, self.OnTestLoadAutoload, id=ID_TEST_LOAD_AUTOLOAD)
        self.Bind(wx.EVT_MENU, self.OnTest00, id=ID_TEST_00)
        self.Bind(wx.EVT_MENU, self.OnTest01, id=ID_TEST_01)
        # Other
        self.Bind(wx.EVT_MENU, self.OnModeTile, id=ID_MODE_TILE)
        self.Bind(wx.EVT_MENU, self.OnModeActor, id=ID_MODE_ACTOR)
        self.Bind(wxpg.EVT_PG_CHANGING, self.OnActorOptionChanging, id=ID_ACTOR_PROPS)
        self.Bind(wxpg.EVT_PG_CHANGED, self.OnActorOptionChanged, id=ID_ACTOR_PROPS)
        self.Bind(wxpg.EVT_PG_CHANGED, self.OnLayerOptionChanged, id=ID_LAYER_OPTIONS)
        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
        self.Bind(wx.EVT_LISTBOX, self.OnLayerListSelection, id=ID_LAYER_LIST)
        self.Bind(wx.EVT_BUTTON, self.OnNewLayer, id=ID_NEW_LAYER)
        self.Bind(wx.EVT_BUTTON, self.OnSceneGridRefresh, id=ID_SCENE_GRID_REFRESH)
        self.UpdateTitle()
        self.UpdateActorPropertyDisplay()
        self.UpdateComponentMenu()
        self.UpdatingComposed = False
        self.UpdatingLayerList = False

    def UpdateData(self):
        self.UpdateTitle()
        self.UpdateLayerList()
        self.UpdateSceneGrid()

    # Level Layers

    def OnLayerListSelection(self, event):
        if self.UpdatingLayerList:
            return
        if not event.IsSelection():
            return
        index = event.GetSelection()
        lindex = event.GetClientObject()
        assert isinstance(lindex, int)
        self.Engine.SelectedLayerIndex = lindex
        self.UpdateLayerProperties()

    def OnLayerOptionChanged(self, event):
        prop = event.GetProperty()
        var = prop.GetClientObject()
        assert isinstance(var, dict)
        layer = self.Engine.SelectedLayer
        try:
            UpdateObjectValue(prop, var, self.Engine.SelectedLayer)
        except Exception:
            UpdateObjectProperty(prop, var, self.Engine.SelectedLayer)
        if prop.GetName() == u'Name':
            self.UpdateLayerList()

    def OnNewLayer(self, event):
        index = wx.GetNumberFromUser(u'', u'Input new layer index', u'New Layer', 0, 0, 32)
        try:
            self.Engine.Level.CreateLayer(index)
        except Exception as ex:
            ShowErrorMessage(u'Failed to create new layer: ' + unicode(ex))
        self.UpdateLayerList()

    def UpdateLayerList(self):
        '''Update the items in the layer list and select Engine.SelectedLayer.'''
        layerlist = self.FindWindowById(ID_LAYER_LIST)
        layerlist.Clear()
        i = 0
        selected = None
        for layer in self.Engine.Level.IterLayers():
            name = layer.Name
            if not name:
                name = u'(Unnamed)'
            lname = u'{0}: {1}'.format(layer.Index, name)
            layerlist.Insert(lname, i, layer.Index)
            if layer.Index == self.Engine.SelectedLayerIndex:
                selected = i
            i += 1
        assert selected is not None
        self.UpdatingLayerList = True
        layerlist.SetSelection(selected)
        self.UpdateLayerProperties()
        self.UpdatingLayerList = False
    
    def UpdateLayerProperties(self):
        '''Update all layer properties from object values.'''
        p = self.LayerProperties.GetPropertyByName(u'Layer')
        layer = self.Engine.SelectedLayer
        UpdateObjectProperties(p, self.LayerVars, layer)

    # Scene grid
    def UpdateSceneGrid(self):
        sg = self.SceneGrid
        if sg.GetNumberRows() > 0:
            sg.DeleteRows(0, sg.GetNumberRows())
        if not self.Engine.Level:
            return
        sg.AppendRows(self.Engine.Level.ActorCount)
        for i, actor in enumerate(self.Engine.Level.IterActors()):
            sg.SetCellValue(i, 0, unicode(actor.ID))
            sg.SetCellValue(i, 1, unicode(actor.Tag) if actor.Tag else u'')
            if actor.Transform:
                sg.SetCellValue(i, 2, unicode(actor.Transform.Position))
            names = []
            for comp in actor.IterComponents():
                names.append(type(comp).__name__)
            if len(names) > 0:
                sg.SetCellValue(i, 3, u', '.join(names))

    def OnSceneGridRefresh(self, event):
        self.UpdateSceneGrid()

    # Actor properties

    def InitActorProperties(self):
        '''Parse variable information for classes and produce properties.'''
        ao = self.ActorProperties
        clslist = GetPlaceableComponentClasses()
        for cls in clslist:
            vars = cls.__varinfo__
            cat = wxpg.PropertyCategory(label=unicode(cls.__name__))
            ao.Append(cat)
            MakeObjectProperties(cat, vars)

    def UpdateActorPropertyDisplay(self):
        '''Updates the display of actor properties.

        This hides or shows categories depending on what components the actor has. If no
        actor is selected, all categories are hidden.
        '''
        if not self.ActorPropertiesInited:
            self.InitActorProperties()
            self.ActorPropertiesInited = True

        if self.Engine.SelectedActor:
            ap = self.ActorProperties
            clsnames = set()
            for comp in self.Engine.SelectedActor.IterComponents():
                assert isinstance(comp, AComponent)
                for cls in type(comp).mro():
                    if cls.__name__ == 'AComponent':
                        break
                    clsnames.add(unicode(cls.__name__))
            ap.HideProperty(ap.GetRoot(), False) # Show all properties
            for cat in ap.GetPyIterator(wxpg.PG_ITERATE_CATEGORIES, wx.TOP):
                if cat.GetLabel().startswith(u'('):
                    continue
                if cat.GetLabel() not in clsnames:
                    ap.HideProperty(cat, True)
        else:
            # Hide all properties
            self.ActorProperties.HideProperty(self.ActorProperties.GetRoot(), True)

    #def EvalActorRef(self, value):
    #    assert self.Engine.Level
    #    if value == u'<None>':
    #        return True, None
    #    # Try seeing if ref is an actor ID
    #    try:
    #        ivalue = int(value)
    #        act = self.Engine.Level.GetActorByID(ivalue)
    #        if not act:
    #            return False, None
    #    except ValueError:
    #        pass

    def UpdateActorPropertyValues(self):
        '''Update the property grid with values from the actor.'''
        act = self.Engine.SelectedActor
        ap = self.ActorProperties
        assert act
        # Base actor props
        ap.GetPropertyByName(u'ID').SetValue(act.ID)
        ap.GetPropertyByName(u'Owner').SetValue(ReprRef(act.Owner))
        ap.GetPropertyByName(u'Tag').SetValue(act.Tag)

        for comp in act.IterComponents():
            clsname = comp.__class__.__name__
            vilist = comp.__class__.__varinfo__
            if not vilist:
                continue
            for vi in vilist:
                vname = unicode(vi['name'])
                propname = unicode(clsname) + u'.' + vname
                prop = ap.GetPropertyByName(propname)
                if not prop:
                    continue
                #print('UPDATING', clsname, vn)
                #self._UpdateActorPropValue(comp, prop, vi)
                UpdateObjectProperty(prop, vi, comp)

    def OnSelectedActorChanged(self, prev, next):
        self.UpdateActorPropertyDisplay()
        self.UpdateActorPropertyValues()

    def UpdateComponentMenu(self):
        assert self.ComponentMenu
        cm = self.ComponentMenu

        # Clear existing menu items
        for i in xrange(cm.GetMenuItemCount() - 1, -1):
            m = cm.FindItemByPosition(i)
            cm.Delete(m)
        assert cm.GetMenuItemCount() == 0

        # Create map of ID to component name starting with firstid
        cids = {}
        compnames = GetPlaceableComponentNames()
        for i, compname in enumerate(compnames):
            id = ID_COMPONENT_FIRST + i
            if id > ID_COMPONENT_LAST:
                raise RuntimeError('component IDs exhausted')
            cids[id] = compname
            cm.Append(id=id, text=unicode(compname))
        self.ComponentIDs = cids

    def OnComponentMenu(self, event):
        id = event.GetMenuId()
        clsname = self.ComponentIDs.get(id, None)
        if clsname is None:
            raise RuntimeError('unknown component ID in menu event: ' + str(id))
        cls = GetComponentClass(clsname)
        if cls is None:
            raise RuntimeError('component class not found for name: ' + clsname)
        if self.Engine.SelectedActor:
            self.Engine.SelectedActor.AddComponent(cls())
            self.UpdateActorPropertyDisplay()

    def OnActorOptionChanging(self, event):
        pass
        #if event.GetMainParent().GetName() == u'Components':
        #    print('ACTOR OPTION CHANGING:', event.GetValue())

    def _ComposeValueFromBools(self, comps):
        labels = []
        for i in xrange(comps.GetChildCount()):
            p = comps.Item(i)
            if p.GetValue():
                labels.append(p.GetLabel())
        if len(labels) > 0:
            return u'; '.join(labels)
        return u''

    def OnActorOptionChanged(self, event):
        pass
        #if event.GetMainParent().GetName() == u'Components':
        #    comps = event.GetMainParent()
        #    if not self.UpdatingComposed:
        #        self.UpdatingComposed = True
        #        if event.GetProperty() != comps:
        #            # We've changed a child, update the parent
        #            comps.SetValue(self._ComposeValueFromBools(comps))
        #        else:
        #            # We've changed the parent string, select the children
        #            compnames = comps.GetValue().split(';')
        #            compnames = set(map(lambda i: i.strip(), compnames))
        #            print('COMPNAMES:', compnames)
        #            for i in xrange(comps.GetChildCount()):
        #                p = comps.Item(i)
        #                if p.GetLabel() in compnames:
        #                    comps.SetValue(True)
        #                else:
        #                    comps.SetValue(False)
        #            #comps.SetValue(self.ComposeValueFromBools(comps))
        #        self.UpdatingComposed = False

    def UpdateTitle(self):
        title = u'Kharlia Level Editor'
        eng = self.Engine
        if eng.LevelPath:
            title += u' ({0})'.format(eng.LevelPath)
        if eng.LevelDirty:
            title += u' *'
        self.Title = title
        
    def OnToggleGrid(self, event):
        self.EdGridVisible = not self.EdGridVisible
        # NOTE: engine.EdGridVisible should be a property
        self.Engine.EdGridVisible(self.EdGridVisible)

    def OnOpenLevel(self, event):
        dialog = wx.FileDialog(
            parent=self,
            message=u'Open level',
            wildcard=u'Level files (*.klv)|*.klv',
            style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
            )
        result = dialog.ShowModal()
        core.Yield()
        if result == wx.ID_CANCEL:
            return
        filename = dialog.GetPath()
        if self.Engine.LevelLoaded:
            self.Engine.ClearLevel(destroy=True)
        self.Engine.LoadLevel(filename)
        #self.UpdateTitle()
        #self.UpdateLayerList()
        self.UpdateData()

    def OnSaveLevel(self, event):
        lpath = self.Engine.LevelPath
        if lpath is None:
            self.OnSaveLevelAs(event)
        else:
            self.Engine.SaveLevel(lpath)
        self.UpdateTitle()
        self.UpdateLayerList()
        
    def OnSaveLevelAs(self, event):
        filename = self.Engine.LevelPath
        if filename is None:
            filename = u''
        dialog = wx.FileDialog(
            parent=self,
            message=u'Save level',
            defaultFile=filename,
            wildcard=u'Level files (*.klv)|*.klv',
            style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT
            )
        result = dialog.ShowModal()
        core.Yield()
        if result == wx.ID_CANCEL:
            return
        filename = dialog.GetPath()
        self.Engine.SaveLevel(filename)
        # TODO: Have this set from the saver
        self.Engine.LevelPath = filename
        self.UpdateTitle()
        self.UpdateLayerList()

    def OnPlay(self, event):
        if not self.Engine.LevelLoaded:
            return
        self.Engine.PlayLevel(FVector3F(5.0, 5.0, 0.0))
        
    def OnEndPlay(self, event):
        if not self.Engine.LevelLoaded:
            return
        self.Engine.EndPlay()

    def OnCloseLevel(self, event):
        self.Engine.ClearLevel(destroy=True)
        self.UpdateTitle()
        self.UpdateLayerList()

    def OnQuit(self,event):
        self.Close(True)

    def OnCloseWindow(self, event):
        self.Hide()
        self.Viewport.Destroy()
        self.Viewport = None
        self.Destroy()
        core.GetApp().RequestExit()

    def OnAbout(self,event):
        pass # open up an about dialog
    
    def OnNewLevel(self, event):
        dialog = CNewLevelDialog(self, callback=self.OnNewLevelDialogOK)
        dialog.CentreOnParent()
        dialog.Show()

    def OnNewLevelDialogOK(self, lname, lw, lh):
        self.Engine.NewLevel(self.Engine.World, None, lname, (lw, lh))
        self.UpdateTitle()
        self.UpdateLayerList()

    def OnModeTile(self, event):
        self.Engine.Mode = CEditorEngine.MODE_TILE

    def OnModeActor(self, event):
        self.Engine.Mode = CEditorEngine.MODE_ACTOR
        
    def OnTestLoadAutoload(self, event):
        self.Engine.LoadLevel(u'autoload.klv')
        
    def OnTest00(self, event):
        pass
        
    def OnTest01(self, event):
        from kharlia.engine.actor import (ATransform, AMovement, ACollider, ACamera,
                                          ASprite, APawn)
        if not self.Engine.LevelLoaded:
            return
        level = self.Engine.Level
        comps = (
                ATransform,
                AMovement,
                ACollider,
                ACamera,
                ASprite,
                APawn
                )
        act = level.CreateActor(comps)
        act.Tag = u'TestActor01'
        act.Transform.Position = (2.0, 2.0, 0.0)
        fname = r'images/icons/W_Sword001.png'
        act.TestResource = self.Engine.Client.GetTexture(fname, fname)
        act.Render.Image = act.TestResource
        print('CREATED TEXTURE:', act.TestResource)

def CheckRectIntersect(recta, rectb):
    if (recta.Right < rectb.Left or
            recta.Left > rectb.Right or
            recta.Bottom < rectb.Top or
            recta.Top > rectb.Bottom):
        return False
    else:
        return True
     #if ((targetRect.GetRight() < destRect.GetLeft()) ||
     #           (targetRect.GetLeft() > destRect.GetRight()) ||
     #           (targetRect.GetBottom() < destRect.GetTop()) ||
     #           (targetRect.GetTop() > destRect.GetBottom())) {
     #           continue;

class CEditorEngine(CEngine):
    MODE_TILE = 0
    MODE_ACTOR = 1

    CamActor = core.CWeakRefDescr('_CamActor')
    _SelectedActor = core.CWeakRefDescr('_SelectedActor_')

    def __init__(self):
        CEngine.__init__(self)
        self.EditorEnabled = True

        self.Frame = None # the top level wx frame
        self._CamActor = None # The actor holding our camera
        self._Level = None # The current level being edited
        self.LevelPath = None # Path to level file
        self.LevelDirty = False # Whether level is dirty and should be saved
        self.SelectedLayerIndex = 0 # Current tile layer
        self.SelectedTiles = None # Selected tile data
        self._SelectedActor_ = None # Selected Actor
        self._Mode = None # Editing mode, tile or actor
        self.EditWorld = None # The world containing the level being edited
        self.PlayWorld = None # The world containing the level being played
        self.PlayActor = None # The actor we've spawned for play
        self.PlayCamera = None # THe camera we're using for play
        #self.EventSelectedActorChanged = core.CEvent()

    def Init(self):
        global GLogger
        GLogger = logging.getLogger('editor')
        GLogger.debug('Initializing editor engine')

        core.CTaskletExt.SetCurrentCritical(True)

        wxsvc = core.GetApp().ServiceManager.GetServiceNoFail('kharlia.wxinter.CWXService')
        if not wxsvc.IsStarted and not wxsvc.IsStarting:
            wxsvc.Start()

        CEngine.Init(self)

        self.Mode = self.MODE_TILE
        self.Frame = wxsvc.CreateTopWindow(CEditorMainFrame, self)
        self.InitClient(self.Frame.Viewport)

        self.World = CWorld()
        self.EditWorld = self.World
        assert self.World.Engine is self
        self.NewLevel(self.World, None, u'TestLevel', (3, 3))

        self.Frame.UpdateLayerList()

    def Exit(self):
        global GLogger
        GEngine = None
        CEngine.Exit(self)
        GLogger.debug('Exiting editor engine')
        GLogger = None

    @property
    def Playing(self):
        return self.PlayWorld

    def MoveCamera(self, delta):
        '''Move the camera by delta.'''
        self.CamActor.Transform.Move(delta)

    @property
    def SelectedActor(self):
        return self._SelectedActor

    def SelectActor(self, actor):
        GLogger.debug('selecting actor: %s', actor)
        prev = self._SelectedActor
        self._SelectedActor = actor
        if prev != next:
            #self.EventSelectedActorChanged(sender=self, prev=prev, next=next)
            self.Frame.OnSelectedActorChanged(prev, next)

    def SelectActorAtScreen(self, pos):
        self.SelectActorAt(self.ConvertScreenToTileCoords(pos))

    def SelectActorAt(self, pos):
        posrect = FRectF(pos.X, pos.Y, 0.0, 0.0)
        candidates = []
        for actor in self.Level.IterActors():
            if not actor.Transform:
                continue
            actorpos = actor.Transform.Position
            if actor.Render and isinstance(actor.Render, ASprite):
                tex = actor.Render.Image
                if tex:
                    sprite_rect = FRectF(actorpos.X, actorpos.Y,
                                                tex.Width / 16.0, tex.Height / 16.0)
                else:
                    sprite_rect = FRectF(actorpos.X, actorpos.Y, 2.0, 2.0)
                if CheckRectIntersect(posrect, sprite_rect):
                    candidates.append(actor)
                    continue
            if actor.Collider:
                bounds = FRectF(actor.Collider.Bounds)
                bounds.X += actorpos.X
                bounds.Y += actorpos.Y
                if CheckRectIntersect(posrect, bounds):
                    candidates.append(actor)
                    continue
            #srect = FRectF(pos.X, pos.Y, 2.0, 2.0)
            #if CheckRectIntersect(posrect, srect):
            #    self.SelectActor(actor)
            #    break

        if len(candidates) < 1:
            return

        try:
            next_selection = candidates.index(self.SelectedActor) + 1
        except ValueError:
            next_selection = 0

        if next_selection >= len(candidates):
            next_selection = 0

        self.SelectActor(candidates[next_selection])

    def CreateActorAtScreen(self, pos):
        return self.CreateActorAt(self.ConvertScreenToTileCoords(pos))

    def CreateActorAt(self, pos):
        assert self.Level
        actor = self.Level.CreateActor(compnames=(ATransform, ASprite))
        actor.Transform.Position = FVector3F(pos.X, pos.Y, 0.0)
        return actor

    @property
    def Mode(self):
        return self._Mode

    @Mode.setter
    def Mode(self, value):
        self._Mode = value
        if value == self.MODE_TILE:
            GLogger.debug('changed to tile edit mode')
            self.TileBrushVisible = True
            self.TileSelectionVisible = True
        elif value == self.MODE_ACTOR:
            GLogger.debug('changed to actor edit mode')
            self.TileBrushVisible = False
            self.TileSelectionVisible = False

    @property
    def SelectedLayer(self):
        if self.Level:
            return self.Level.GetLayer(self.SelectedLayerIndex)
        return None

    @property
    def TileBrush(self):
        '''Get the current tile brush as FTileData.'''
        return self.EditorBrush

    @TileBrush.setter
    def TileBrush(self, tiledata):
        '''Create and set the current tile brush from a list of tile codes.'''
        if isinstance(tiledata, FTileData):
            self.EditorBrush = tiledata
        else:
            try:
                width, height, tilecodes = tiledata
                self.EditorBrush = FTileData(FSizeI(width, height), tilecodes)
            except ValueError:
                raise ValueError('tiledata must be an instance of FTileData or a sequence'
                                 'of width, height, and tile codes.')
        #self.EditorBrushVisible = True

    @TileBrush.deleter
    def TileBrush(self):
        '''Clear the tile brush.'''
        self.EditorBrush = None
        #self.EditorBrushVisible = False

    @property
    def TileBrushVisible(self):
        return self.EditorBrushVisible

    @TileBrushVisible.setter
    def TileBrushVisible(self, value):
        self.EditorBrushVisible = value

    def ApplyTileBrush(self, pos):
        '''Paste the tiles in the current brush at pos on the current layer.'''
        self.Level.SetTiles(self.SelectedLayerIndex, pos, self.EditorBrush)
        self.LevelDirty = True

    @property
    def TileSelection(self):
        '''Get the selection rect.'''
        return self.EditorSelection

    @TileSelection.setter
    def TileSelection(self, selection):
        '''Set the selection rect.'''
        self.EditorSelection = selection
        #self.EditorSelectionVisible = True
    
    @TileSelection.deleter
    def TileSelection(self):
        '''Clear the selection rect.'''
        self.EditorSelection = FRectF()
        #self.EditorSelectionVisible = False

    @property
    def TileSelectionVisible(self):
        return self.EditorSelectionVisible

    @TileSelectionVisible.setter
    def TileSelectionVisible(self, value):
        self.EditorSelectionVisible = value

    def ConvertScreenToTileCoords(self, pos):
        '''Convert screen coordinates in pixels to tile coordinates.
        
        Returns FVector2F'''
        return self.Client.ConvertScreenToTileCoords(pos)

    def MakeDefaultTileset(self):
        assert self.Client
        tsname = 'images/tiles/testtiles.png'
        ts = FTileset('Default', 1)
        ts.Texture = self.Client.GetTexture(tsname, tsname)
        return ts

    def NewLevel(self, world, guid, name, size, defaulttile=16):
        '''Create a new level and set it as the current level.'''
        if self.LevelLoaded:
            self.ClearLevel(destroy=True)
        #level = world.CreateLevel(guid, name, size)
        level = CLevel(guid, name, size)
        world.AddLevel(level)
        level.CreateLayer(0)
        level.SetTiles(0, defaulttile)
        for layer in level.IterLayers():
            if not layer.Tileset:
                layer.Tileset = self.MakeDefaultTileset()
        self.Level = level
        self.LevelDirty = True
        return level

    def ClearLevel(self, destroy=False):
        '''Clear the current level and optionally destroy it.
    
        Returns the cleared level object if not destroyed. Also removes the
        transient camera.
        '''
        assert self.World
        assert self.Level
        self.LevelDirty = False
        self.LevelPath = None
        if destroy:
            lev = self.Level
            self.World.RemoveLevel(lev)
            lev.Clear()
        else:
            lev = self.Level
            self.Level.DestroyActor(self.CamActor)
        import gc
        self._Level = None
        self.CamActor = None
        assert self.Client
        #assert not self.Client.Camera
        self.Client.Camera = None
        return lev

    @property
    def Level(self):
        return self._Level

    @Level.setter
    def Level(self, level):
        '''Set level as the currently viewed level. Creates the editor camera.'''
        assert self.Client
        assert not self.Level
        self._Level = level
        self.World = level.World
        cam = self.CreateEditorCamera(level)
        self.CamActor = cam
        self.Client.Camera = cam.Camera

    def LoadLevel(self, filepath, name=None):
        '''Load a level package and set it as the current level.
    
        Destroys the current level if one is set.
        '''
        assert self.World

        GLogger.debug('loading level from %s', filepath)
    
        # Clear existing level
        if self.LevelLoaded:
            GLogger.debug('destroying current level')
            ClearLevel(destroy=True)
        assert not self.Level
        
        # Open file and load the level package from it
        resmgr = self.ResourceManager
        with io.open(filepath, 'rb') as file:
            lev, resources = CWorld.LoadLevelPackage(file, resmgr, name)
        GLogger.debug('unpickling complete, loading resources')
    
        # Load referenced resources and report failures
        fails = self.LoadResources(resources)
        if len(fails) > 0:
            GLogger.error('failed to load %i resources', len(fails))
        
        #if 0: # Incomplete tileset code
        #    if not lev.Tileset:
        #        lev.Tileset = self.MakeDefaultTileset()
            
        # Add our level to the world, and update the editor to view it
        self.World.AddLevel(lev)
        self.Level = lev
        self.LevelDirty = False
        if not isinstance(filepath, unicode):
            filepath = unicode(filepath)
        self.LevelPath = filepath
        GLogger.debug('finished load')

    def SaveLevel(self, filepath):
        '''Save the current level to a package. Overwrites destination.'''
        assert self.LevelLoaded
    
        GLogger.debug('saving level to %s', filepath)
        filestr = cstringio.StringIO()
        CWorld.SaveLevelPackage(self.Level, filestr)
        with io.open(filepath, 'wb') as file:
            file.write(filestr.getvalue())
        self.LevelDirty = False
        GLogger.debug('finished save')

    @property
    def LevelLoaded(self):
        '''Check if a level is currently loaded.'''
        if self.Level:
            assert self.World
            assert self.CamActor
            return True
        else:
            return False

    def CreateEditorCamera(self, level):
        '''Create the transient editor camera actor. Returns the new actor.'''
        cam = level.CreateActor((ATransform, ACamera))
        cam.Tag = u'EditorCamera'
        cam.IsTransient = True
        return cam

    def PlayLevel(self, pos):
        '''
        Begin gameplay with the current level. Pos specifies the position
        to spawn at.
    
        The current level will be copied and moved to a new world for play.
        '''
        if self.Playing:
            self.EndPlay()
        assert not self.Playing
        assert self.LevelLoaded
        assert self.Level.World is self.World
        world = None
        try:
            # Levels can currently be copied by pickling then unpickling
            from cStringIO import StringIO
            import testgame
        
            # Pickle current editing level
            sio = StringIO()
            CWorld.PickleLevel(self.Level, sio)
            sio.seek(0)
        
            # Create the play world
            #raise NotImplementedError()
            world = CWorld()
    
            # Unpickle level into the world
            level, resources = CWorld.UnpickleLevel(sio, self.ResourceManager)
            self.LoadResources(resources)
            world.AddLevel(level)
            assert level.World is world
            
            self.World = world
            assert world.Engine is self

            # Start the game
            #world.GameClass = engine.CGame
            world.GameClass = testgame.CTestGame
            world.StartGame()
    
            # Create the player controller
            pc = world.CreatePlayerActor(self.LocalPlayer)
            assert isinstance(pc, APlayerController)
    
            # Create the player actor
            pawncomps = (
                ATransform,
                AMovement,
                ACollider,
                ACamera,
                ASprite,
                APawn
                )
    
            pawnact = level.CreateActor(pawncomps)
            pawnact.Tag = u'EditorPlayer'
            pawnact.Transform.Position = pos
        
            # Possess the pawn
            pc.Possess(pawnact.Pawn)
        
            # Set up movement target
            #GState.Engine.TestActor = pawnact
            #GState.Engine.TestMovement = pawnact.Movement
        
            # Switch to the play world
            #GState.Engine.World = world
            #GState.Client.Camera = pawnact.Camera
            self.Client.Camera = pawnact.Camera
    
            self.PlayWorld = world
            self.PlayActor = pc.Actor
            self.PlayCamera = pawnact.Camera

        except Exception as e:
            GLogger.error('unable to create play world: %s', e)
            raise
        
    def EndPlay(self):
        assert self.Playing
        assert self.World is not self.Level.World
        assert self.EditWorld and self.EditWorld is not self.World
    
        #self.TestActor = None
        #self.TestMovement = None
    
        self.World = self.EditWorld
        self.Client.Camera = self.CamActor.Camera
    
        self.PlayCamera = None
        self.PlayActor = None
        self.PlayWorld.ClearLevels(True)
        self.PlayWorld.Clear()
        self.PlayWorld = None